//go:build linux

package websocket

import (
	"golang.org/x/sys/unix"
	"net"
	"sync"
)

type Server struct {
	eventHandler Event
	option       *ServerOption
	numLoops     uint64
	serial       uint64
	loops        []*EventLoop
}

func NewServer(eventHandler Event, option *ServerOption) *Server {
	if option == nil {
		option = new(ServerOption)
	}
	option.initialize()

	return &Server{
		eventHandler: eventHandler,
		option:       option,
		numLoops:     8,
	}
}

func (c *Server) init() error {
	for i := uint64(0); i < c.numLoops; i++ {
		loop, err := NewEventLoop(c.eventHandler, c.option)
		if err != nil {
			return err
		}
		c.loops = append(c.loops, loop)
		loop.Start()
	}
	return nil
}

func (c *Server) Run(addr string) error {
	if err := c.init(); err != nil {
		return err
	}

	listener, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	file, err := listener.(*net.TCPListener).File()
	if err != nil {
		return err
	}

	for {
		fd, _, err := unix.Accept(int(file.Fd()))
		if err != nil {
			continue
		}
		if err := unix.SetNonblock(fd, true); err != nil {
			continue
		}

		//fmt.Printf("accept fd=%d\n", fd)
		idx := uint64(fd) & (c.numLoops - 1)
		if err := c.loops[idx].Add(fd); err != nil {
			continue
		}
	}
}

type EventLoop struct {
	sync.Mutex
	eventHandler Event
	option       *ServerOption
	epfd         int
	events       []unix.EpollEvent
	connections  map[int]*Conn
	numEvents    int
}

func NewEventLoop(eventHandler Event, option *ServerOption) (*EventLoop, error) {
	epfd, err := unix.EpollCreate1(0)

	if err != nil {
		return nil, err
	}
	return &EventLoop{
		eventHandler: eventHandler,
		option:       option,
		epfd:         epfd,
		numEvents:    1024,
		events:       make([]unix.EpollEvent, 1024),
		connections:  make(map[int]*Conn),
	}, nil
}

func (c *EventLoop) Start() {
	go func() {
		for {
			if err := c.Wait(); err != nil {
				continue
			}
		}
	}()
}

func (c *EventLoop) Add(fd int) error {
	events := &unix.EpollEvent{
		Events: unix.EPOLLIN | unix.EPOLLHUP | unix.EPOLLERR | unix.EPOLLET,
		Fd:     int32(fd),
	}
	if err := unix.EpollCtl(c.epfd, unix.EPOLL_CTL_ADD, fd, events); err != nil {
		return err
	}

	c.Lock()
	c.connections[fd] = NewConn(fd, c.eventHandler, c.option)
	c.Unlock()
	return nil
}

func (c *EventLoop) Remove(fd int) error {
	c.Lock()
	if _, ok := c.connections[fd]; !ok {
		c.Unlock()
		return nil
	}
	delete(c.connections, fd)
	c.Unlock()

	return unix.EpollCtl(c.epfd, unix.EPOLL_CTL_DEL, fd, nil)
}

func (c *EventLoop) Wait() error {
retry:
	n, err := unix.EpollWait(c.epfd, c.events, c.numEvents)
	if err != nil {
		if err == unix.EINTR {
			goto retry
		}
		return err
	}

	for i := 0; i < n; i++ {
		var event = &c.events[i]
		if event.Fd == 0 {
			break
		}

		var fd = int(event.Fd)
		event.Fd = 0
		c.Lock()
		conn, ok := c.connections[fd]
		c.Unlock()
		if ok {
			if event.Events&unix.EPOLLIN != 0 {
				if err := conn.readMessage(); err != nil {
					c.Remove(fd)
					continue
				}
			}
			if event.Events&unix.EPOLLHUP != 0 || event.Events&unix.EPOLLERR != 0 {
				c.Remove(fd)
				continue
			}
		}
	}
	return nil
}
